package com.xd.center.http.builder;

import com.xd.center.http.annotation.HttpClient;
import com.xd.center.http.annotation.HttpHeader;
import com.xd.center.http.annotation.HttpLine;
import com.xd.center.http.annotation.HttpParam;
import com.xd.center.http.vo.AnnotationProcessorVo;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;

public interface HttpContract {

  Map<Method, MethodMetadata> parseAndValidateMetadata(Class<?> c);

  class Default implements HttpContract {
    private List<AnnotationProcessorVo> classAnnotationProcessorList = new ArrayList<>();
    private List<AnnotationProcessorVo> methodAnnotationProcessorList = new ArrayList<>();
    private List<AnnotationProcessorVo> paramAnnotationProcessorList = new ArrayList<>();
    private Map<Method, MethodMetadata> methodMetadataMap = new ConcurrentHashMap<>();

    public Default() {
      registerClassAnnotation(HttpClient.class, (annotation, metadata) -> metadata.setServer(annotation.value()));

      registerMethodAnnotation(HttpHeader.class, (annotation, metadata) -> {
        metadata.setHeaders(annotation.value());
        metadata.setOpenAuth(annotation.openAuth());
        metadata.setConsumes(annotation.consumes());
        metadata.setProduces(annotation.produces());
      });

      registerMethodAnnotation(HttpLine.class, (annotation, metadata) -> {
        metadata.setHttpMethod(annotation.method());
        metadata.setDefaultUrl(annotation.value());
        metadata.setUrl(annotation.value());
        metadata.setRepeatInvoke(annotation.repeatInvoke());
        metadata.setRepeatInvokeField(annotation.repeatInvokeField());
      });

      registerParamAnnotation(HttpParam.class, (annotation, metadata, index) -> {
        metadata.getParamNameMap().put(index, annotation.value());
      });
    }

    @Override
    public Map<Method, MethodMetadata> parseAndValidateMetadata(Class<?> c) {
      //解析类注解
      MethodMetadata classMetadata = new MethodMetadata();
      Arrays.stream(c.getAnnotations())
        .forEach(annotation -> classAnnotationProcessorList.stream()
          .filter(processor -> processor.getPredicate().test(annotation))
          .forEach(processor -> processor.getProcessor().process(annotation, classMetadata))
        );

      Arrays.stream(c.getMethods())
        .filter(method -> !(method.getDeclaringClass() == Object.class || (method.getModifiers() & Modifier.STATIC) != 0 || isDefault(method)))
        .forEach(method -> {
          MethodMetadata methodMetadata = new MethodMetadata();

          //解析方法注解
          Arrays.stream(method.getAnnotations())
            .forEach(annotation -> methodAnnotationProcessorList.stream()
              .filter(processor -> processor.getPredicate().test(annotation))
              .forEach(processor -> {
                methodMetadata.setServer(classMetadata.getServer());
                processor.getProcessor().process(annotation, methodMetadata);
              }));


          //验证并解析参数注解
          Parameter[] parameters = method.getParameters();
          Class<?>[] parameterTypes = method.getParameterTypes();
          Annotation[][] parameterAnnotations = method.getParameterAnnotations();
          for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Class parameterType = parameterTypes[i];
            Annotation[] annotations = parameterAnnotations[i];

            boolean existsParam = Boolean.FALSE;
            for (Annotation annotation : annotations) {
              for (AnnotationProcessorVo processor : paramAnnotationProcessorList) {
                if (processor.getPredicate().test(annotation)) {
                  existsParam = Boolean.TRUE;
                  processor.getParamProcessor().process(annotation, methodMetadata, i);
                }
              }
            }

            if (!existsParam && null == parameterType.getClassLoader() && isBasicTypes(parameterType)) {
              throw new RuntimeException(method.getName() + "#" + parameter.getName() + "@" + parameterType.getName() + " method param not found Annotation(@HttpParam)");
            }
          }

          /*AtomicInteger i = new AtomicInteger(0);
          Arrays.stream(method.getParameterAnnotations())
            .forEach(annotations -> Arrays.stream(annotations)
              .forEach(annotation -> paramAnnotationProcessorList.stream()
                .filter(processor -> processor.getPredicate().test(annotation) && null != processor.getParamProcessor())
                .forEach(processor -> processor.getParamProcessor().process(annotation, methodMetadata, i.getAndIncrement()))
              )
            );*/

          methodMetadataMap.put(method, methodMetadata);
        });

      return methodMetadataMap;
    }

    public <A extends Annotation> void registerClassAnnotation(Class<A> annotationClass, AnnotationProcessor<A> annotationProcessor) {
      Predicate<A> predicate = annotation -> annotation.annotationType().equals(annotationClass);
      classAnnotationProcessorList.add(new AnnotationProcessorVo(predicate, annotationProcessor));
    }

    public <A extends Annotation> void registerMethodAnnotation(Class<A> annotationClass, AnnotationProcessor<A> annotationProcessor) {
      Predicate<A> predicate = annotation -> annotation.annotationType().equals(annotationClass);
      methodAnnotationProcessorList.add(new AnnotationProcessorVo(predicate, annotationProcessor));
    }

    public <A extends Annotation> void registerParamAnnotation(Class<A> annotationClass, ParamAnnotationProcessor<A> annotationProcessor) {
      Predicate<A> predicate = annotation -> annotation.annotationType().equals(annotationClass);
      paramAnnotationProcessorList.add(new AnnotationProcessorVo(predicate, annotationProcessor));
    }
  }

  interface AnnotationProcessor<A extends Annotation> {
    void process(A annotation, MethodMetadata metadata);
  }


  interface ParamAnnotationProcessor<A extends Annotation> {
    void process(A annotation, MethodMetadata metadata, int index);
  }

  static boolean isDefault(Method method) {
    // Default methods are public non-abstract, non-synthetic, and non-static instance methods
    // declared in an interface.
    // method.isDefault() is not sufficient for our usage as it does not check
    // for synthetic methods. As a result, it picks up overridden methods as well as actual default
    // methods.
    final int SYNTHETIC = 0x00001000;
    return ((method.getModifiers()
      & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC | SYNTHETIC)) == Modifier.PUBLIC)
      && method.getDeclaringClass().isInterface();
  }

  static Boolean isBasicTypes(Object o) {
    Class cls = o instanceof Class ? (Class) o : o.getClass();
    if (cls.isPrimitive() || o instanceof Byte || o instanceof Short || o instanceof Integer || o instanceof Long || o instanceof Float || o instanceof Double || o instanceof Character || o instanceof Boolean || o instanceof String) {
      return Boolean.TRUE;
    }
    if (cls == Byte.class || cls == Short.class || cls == Integer.class || cls == Long.class || cls == Float.class || cls == Double.class || cls == Character.class || cls == Boolean.class || cls == String.class) {
      return Boolean.TRUE;
    }
    return Boolean.FALSE;
  }
}
